using System;
using System.Collections;
using System.IO;

namespace System.Windows.Media.DirectShow.Vorbis
{

	public class AudioPacket {
	
	   private int modeNumber;
	   private Mode mode;
	   private Mapping mapping;
	   private int n; // block size
	   private bool blockFlag, previousWindowFlag, nextWindowFlag;
	   private int windowCenter, leftWindowStart, leftWindowEnd, leftN, rightWindowStart, rightWindowEnd, rightN;
	   private float[] window;
	   private float[][] pcm;
	   private int[][] pcmInt;
	
	   private Floor[] channelFloors;
	   private bool[] noResidues;
	
	   private static float[][] windows=new float[8][];
	
		public AudioPacket(VorbisStream vorbis, IBitStream source) {
	
	      SetupHeader sHeader=vorbis.SetupHeader;
	      IdentificationHeader iHeader=vorbis.IdentificationHeader;
	      Mode[] modes=sHeader.Modes;
	      Mapping[] mappings=sHeader.Mappings;
	      Residue[] residues=sHeader.Residues;
	      int channels=iHeader.Channels;
	
	      if(source.ReadBit()) {
	         throw new VorbisFormatException("Packet type mismatch when trying to create an audio packet.");
	      }
	
	      modeNumber=source.ReadInt32(Util.Ilog(modes.Length-1));
	
	      try {
	         mode=modes[modeNumber];
	      }
	      catch(IndexOutOfRangeException) {
	         throw new VorbisFormatException("Reference to invalid mode in audio packet.");
	      }
	
	      mapping=mappings[mode.Mapping];
	
	      int[] magnitudes=mapping.Magnitudes;
	      int[] angles=mapping.Angles;
	
	      blockFlag=mode.BlockFlag;
	
	      int blockSize0=iHeader.BlockSize0;
	      int blockSize1=iHeader.BlockSize1;
	
	      n=blockFlag?blockSize1:blockSize0;
	
	      if(blockFlag) {
	         previousWindowFlag=source.ReadBit();
	         nextWindowFlag=source.ReadBit();
	      }
	
	      windowCenter=n/2;
	
	      if(blockFlag && !previousWindowFlag) {
	         leftWindowStart=n/4-blockSize0/4;
	         leftWindowEnd=n/4+blockSize0/4;
	         leftN=blockSize0/2;
	      }
	      else {
	         leftWindowStart=0;
	         leftWindowEnd=n/2;
	         leftN=windowCenter;
	      }
	
	      if(blockFlag && !nextWindowFlag) {
	         rightWindowStart=n*3/4-blockSize0/4;
	         rightWindowEnd=n*3/4+blockSize0/4;
	         rightN=blockSize0/2;
	      }
	      else {
	         rightWindowStart=windowCenter;
	         rightWindowEnd=n;
	         rightN=n/2;
	      }
	
	      window=GetComputedWindow();//new double[n];
	
	      channelFloors=new Floor[channels];
	      noResidues=new bool[channels];
	
	      pcm=new float[channels][];
	      for(int i=0; i<channels; i++) {
	      	pcm[i]=new float[n];	
	      }
	      
	      pcmInt=new int[channels][];
	      for(int i=0; i<channels; i++) {
	      	pcmInt[i]=new int[n];	
	      }
	
	      bool allFloorsEmpty=true;
	
	      for(int i=0; i<channels; i++) {
	         int submapNumber=mapping.Mux[i];
	         int floorNumber=mapping.SubmapFloors[submapNumber];
	         Floor decodedFloor=sHeader.Floors[floorNumber].DecodeFloor(vorbis, source);
	         channelFloors[i]=decodedFloor;
	         noResidues[i]=decodedFloor==null;
	         if(decodedFloor!=null) {
	            allFloorsEmpty=false;
	         }
	      }
	
	      if(allFloorsEmpty) {
	         return;
	      }
	
	      for(int i=0; i<magnitudes.Length; i++) {
	         if(!noResidues[magnitudes[i]] ||
	            !noResidues[angles[i]]) {
	
	            noResidues[magnitudes[i]]=false;
	            noResidues[angles[i]]=false;
	         }
	      }
	
	      Residue[] decodedResidues=new Residue[mapping.Submaps];
	
	      for(int i=0; i<mapping.Submaps; i++) {
	         int ch=0;
	         bool[] doNotDecodeFlags=new bool[channels];
	         for(int j=0; j<channels; j++) {
	            if(mapping.Mux[j]==i) {
	               doNotDecodeFlags[ch++]=noResidues[j];
	            }
	         }
	         int residueNumber=mapping.SubmapResidues[i];
	         Residue residue=residues[residueNumber];
	
	         residue.DecodeResidue(vorbis, source, mode, ch, doNotDecodeFlags, pcm);
	      }
	
	
	      for(int i=mapping.CouplingSteps-1; i>=0; i--) {
	         float[] magnitudeVector=pcm[magnitudes[i]];
	         float[] angleVector=pcm[angles[i]];
	         for(int j=0; j<magnitudeVector.Length; j++) {
	            float a=angleVector[j];
	            float m=magnitudeVector[j];
	            if(a>0) {
	               //magnitudeVector[j]=m;
	               angleVector[j]=m>0?m-a:m+a;
	            }
	            else {
	               magnitudeVector[j]=m>0?m+a:m-a;
	               angleVector[j]=m;
	            }
	         }
	      }
	
	      for(int i=0; i<channels; i++) {
	         if(channelFloors[i]!=null) {
	            channelFloors[i].ComputeFloor(pcm[i]);
	         }
	         //else {
	         //   for(int j=0; j<pcm[i].length; j++) {
	         //      pcm[i][j]=0.0;
	         //   }
	         //}
	      }
	      // perform an inverse mdct to all channels
	      for(int i=0; i<channels; i++) {
	         MdctFloat mdct=blockFlag?iHeader.Mdct1:iHeader.Mdct0;
	         mdct.Imdct(pcm[i], window, pcmInt[i]);
	      }
	
	   }
	
	   private float[] GetComputedWindow() {
	      int ix=(blockFlag?4:0)+(previousWindowFlag?2:0)+(nextWindowFlag?1:0);
	      float[] w=windows[ix];
	      if(w==null) {
	         w=new float[n];
	
	         for(int i=0;i<leftN;i++){
		         float x=(float)((i+0.5)/leftN*Math.PI/2.0);
		         x=(float)Math.Sin(x);
		         x*=x;
		         x*=(float)Math.PI/2.0f;
		         x=(float)Math.Sin(x);
		         w[i+leftWindowStart]=x;
		      }
	
	         for(int i=leftWindowEnd; i<rightWindowStart; w[i++]=1.0f);
	
		      for(int i=0;i<rightN;i++){
		         float x=(float)((rightN-i-0.5)/rightN*Math.PI/2.0);
		         x=(float)Math.Sin(x);
		         x*=x;
		         x*=(float)Math.PI/2.0f;
		         x=(float)Math.Sin(x);
		         w[i+rightWindowStart]=x;
		      }
	
	         windows[ix]=w;
	      }
	      return w;
	   }
	
	   public int GetPcm(AudioPacket previousPacket, int[][] buffer) {
	      int channels=pcm.Length;
	      int val;
	
	      // copy left window flank and mix with right window flank from
	      // the previous audio packet
	      for(int i=0; i<channels; i++) {
	         int j1=0, j2=previousPacket.rightWindowStart;
	         int[] ppcm=previousPacket.pcmInt[i];
	         int[] tpcm=pcmInt[i];
	         int[] target=buffer[i];
	         for(int j=leftWindowStart; j<leftWindowEnd; j++) {
	            val=ppcm[j2++]+tpcm[j];
	            if(val>32767) val=32767;
	            if(val<-32768) val=-32768;
	            target[j1++]=val;
	         }
	      }
	
	      // use System.arraycopy to copy the middle part (if any)
	      // of the window
	      for(int i=0; i<channels; i++) {
	         Array.Copy(pcmInt[i], leftWindowEnd, buffer[i], leftWindowEnd-leftWindowStart, rightWindowStart-leftWindowEnd);
	      }
	
	      return rightWindowStart-leftWindowStart;
	   }
	
	   public float[] Window {
	   	get {
	      	return window;
	      }
	   }
	
	   public int LeftWindowStart {
	   	get {
	      	return leftWindowStart;
	      }
	   }
	
	   public int LeftWindowEnd {
	   	get {
	      	return leftWindowEnd;
	      }
	   }
	
	   public int RightWindowStart {
	   	get {
	      	return rightWindowStart;
	      }
	   }
	
	   public int RightWindowEnd {
	   	get {
	      	return rightWindowEnd;
	      }
	   }
	}
	
}